package com.freesky.biz.citic.utils;

import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;

public class SMUtils {
    public static final String ALGORITHM_NAME = "SM4";

    public static final int DEFAULT_KEY_SIZE = 128;

    public static final String ENCRYPT_ALGORITHM_NAME = "SM4/ECB/PKCS7PADDING";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * @param genP10     是否同步生成csr，如需要，以下三个字段必输
     * @param enterprise 企业名称
     * @param creditCode 统一社会信用代码
     * @param email      开发者邮箱
     * @MethodName: genSM2KeyPair
     * @Description: 生成sm2密钥对及csr（p10）
     * @Return: java.util.HashMap<java.lang.String, java.lang.String>  公钥key：publicKey， 私钥key：privateKey，csr的key：csr
     * @version: 1.0
     */
    public static HashMap<String, String> genSM2KeyPair(boolean genP10, String enterprise, String creditCode, String email) throws
            NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, OperatorCreationException, IOException {
        HashMap<String, String> result = new HashMap<>();

        ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        SecureRandom random = new SecureRandom();
        kpg.initialize(sm2Spec, random);
        KeyPair kp = kpg.genKeyPair();

        String publicKeyStr = Base64.toBase64String(kp.getPublic().getEncoded());
        String privateKeyStr = Base64.toBase64String(kp.getPrivate().getEncoded());
        result.put("publicKey", publicKeyStr);
        result.put("privateKey", privateKeyStr);

        if (genP10) {
            X500NameBuilder localX500NameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
            localX500NameBuilder.addRDN(BCStyle.O, enterprise);
            localX500NameBuilder.addRDN(BCStyle.UID, creditCode);
            localX500NameBuilder.addRDN(BCStyle.E, email);
            X500Name localX500Name = localX500NameBuilder.build();
            JcaPKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(localX500Name, kp.getPublic());
            JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SM3WITHSM2");
            ContentSigner signer = csBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(kp.getPrivate());
            PKCS10CertificationRequest csr = p10Builder.build(signer);
            String p10Base64 = Base64.toBase64String(csr.getEncoded());
            result.put("csr", p10Base64);
        }
        return result;
    }

    /**
     * @param publicKeyStr 公钥
     * @param data         待加密数据
     * @MethodName: sm2Encrypt
     * @Description: SM2加密方法
     * @Return: java.lang.String
     * @version: 1.0
     */
    public static String sm2Encrypt(String publicKeyStr, String data) throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidCipherTextException {

        byte[] keyBytes = Base64.decode(publicKeyStr);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
        ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
        //使用bcprov-jdk14，请使用如下初始化
        //SM2Engine sm2Engine = new SM2Engine(SM2Engine.C1C3C2);
        //使用bcprov-jdk15on、bcprov-jdk15to18，请使用如下初始化
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters));

        byte[] in = data.getBytes(StandardCharsets.UTF_8);
        byte[] arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);

        return Base64.toBase64String(arrayOfBytes);
    }

    /**
     * @param privateKeyStr 私钥
     * @param cipherData    加密数据
     * @MethodName: sm2Decrypt
     * @Description: SM2解密方法
     * @Return: java.lang.String
     * @version: 1.0
     */
    public static String sm2Decrypt(String privateKeyStr, String cipherData) throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidCipherTextException {

        byte[] keyBytes = Base64.decode(privateKeyStr);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

        BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
        ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(),
                ecDomainParameters);
        //使用bcprov-jdk14，请使用如下初始化
        //SM2Engine sm2Engine = new SM2Engine(SM2Engine.C1C3C2);
        //使用bcprov-jdk15on、bcprov-jdk15to18，请使用如下初始化
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, ecPrivateKeyParameters);

        byte[] in = Base64.decode(cipherData);
        byte[] arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        String result = new String(arrayOfBytes, StandardCharsets.UTF_8);

        return result;
    }

    /**
     * @param bytes 待摘要原文字节数组
     * @MethodName: sm3Hash
     * @Description: sm3摘要处理方法
     * @Return: byte[]
     * @version: 1.0
     */
    public static byte[] sm3Hash(byte[] bytes) {
        SM3Digest sm3 = new SM3Digest();
        sm3.update(bytes, 0, bytes.length);
        byte[] result = new byte[sm3.getDigestSize()];
        sm3.doFinal(result, 0);
        return result;
    }

    /**
     * @param
     * @MethodName: genSm4Key
     * @Description: 生成sm4对称加密key
     * @Return: java.lang.String
     * @Throws:
     * @version: 1.0
     */
    public static String genSm4Key() throws NoSuchAlgorithmException, NoSuchProviderException {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        SecureRandom random = new SecureRandom();
        kg.init(DEFAULT_KEY_SIZE, random);
        return Base64.toBase64String(kg.generateKey().getEncoded());
    }

    /**
     * @param key  对称密钥，32位16进制密钥
     * @param data 待加密数据，base64编码数据
     * @MethodName: sm4Encrypt
     * @Description: sm4对称加密算法，默认采用SM4/ECB/PKCS7PADDING
     * @Return: java.lang.String base64编码密文
     * @version: 1.0
     */
    public static String sm4Encrypt(String key, String data) throws Exception {
        return Base64.toBase64String(sm4core(Cipher.ENCRYPT_MODE, Base64.decode(key), Base64.decode(data)));
    }

    /**
     * @param key    对称密钥，32位16进制密钥
     * @param cipher 待解密数据，base64编码密文
     * @MethodName: sm4Decrypt
     * @Description: sm4对称解密算法，默认采用SM4/ECB/PKCS7PADDING
     * @Return: java.lang.String base64编码原文
     * @version: 1.0
     */
    public static String sm4Decrypt(String key, String cipher) throws Exception {
        return Base64.toBase64String(sm4core(Cipher.DECRYPT_MODE, Base64.decode(key), Base64.decode(cipher)));
    }

    private static byte[] sm4core(int type, byte[] key, byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(type, sm4Key);
        return cipher.doFinal(data);
    }

    /**
     * @param originBytes   待签名数据
     * @param privateKeyStr 私钥
     * @MethodName: sign
     * @Description: 国密签名方法
     * @Return: java.lang.String
     * @version: 1.0
     */
    public static String sign(byte[] originBytes, String privateKeyStr) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException, NoSuchProviderException, InvalidKeySpecException {
        // 创建签名对象
        Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        byte[] keyBytes = Base64.decode(privateKeyStr);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

        BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
        // 初始化为签名状态
        signature.initSign(bcecPrivateKey);
        // 传入签名字节
        signature.update(originBytes);
        // 签名
        byte[] result = signature.sign();
        return Base64.toBase64String(result);
    }

    /**
     * @param caCert   验签证书
     * @param userCert 待验签证书
     * @MethodName: verifyUserCert
     * @Description: 用根证书验签用户证书有效性
     * @Return: boolean
     * @Throws:
     * @version: 1.0
     */
    public static boolean verifyUserCert(String caCert, String userCert) {
        try {
            X509Certificate caX509Cert = genX509Cert(caCert);
            X509Certificate userX509Cert = genX509Cert(userCert);
            userX509Cert.verify(caX509Cert.getPublicKey());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public static boolean certVerify(String cert, byte[] originBytes, String signedB64Str)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, CertificateException, NoSuchProviderException {

        byte[] signValue = Base64.decode(signedB64Str);
        /*
         * 解析证书
         */
        X509Certificate certificate = genX509Cert(cert);
        // 验证签名
        Signature signature = Signature.getInstance(certificate.getSigAlgName(), BouncyCastleProvider.PROVIDER_NAME);
        signature.initVerify(certificate);
        signature.update(originBytes);
        return signature.verify(signValue);
    }

    public static X509Certificate genX509Cert(String cert) throws CertificateException {
        CertificateFactory factory = new CertificateFactory();
        X509Certificate certificate = (X509Certificate) factory
                .engineGenerateCertificate(new ByteArrayInputStream(Base64.decode(cert)));
        return certificate;
    }

}
